#include <ctime>
#include "miniutil/runtimeu.h"
#include "miniutil/srtti.h"
#include "miniutil/logservice.h"
#include "miniutil/diru.h"

/*
	Update:
		100930  add AotuRunTimeLogger which update entire log file.
*/
/*
config file:

    "<?xml version='1.0'?>"
    "<loggers>"
        "<logger name='consolelogger' >"
            "<config level='debug|crit|trace'></config>"
        "</logger>"
    "</loggers>";

    for filelogger, it support following configuration

    "<?xml version='1.0'?>"
    "<loggers>"
        "<logger name='filelogger' >"
            "<config level='debug|crit|trace' maxfilesize='' 
                maxfilenum='' filenmprefix='' savedir=''>"
            "</config>"
        "</logger>"
    "</loggers>";

    for tagfilelogger, it support tag configuration

    "<?xml version='1.0'?>"
    "<loggers>"
        "<logger name='taglogger' >" <!-- taglogger same as tagfilelogger -->
            "<config>"
            "<filelogger tag="" printtag="false">
            "<config level='debug|crit|trace' maxfilesize='' 
                maxfilenum='' filenmprefix='' savedir='' usepid='true'>"
            "</config>"
            "</filelogger>"
            "<consolelogger tag='keke|xixi'>"
                "<config append='time|thread'></config>"
            "</consolelogger>"
            "<config>"
        "</logger>"
    "</loggers>";

	the config of autotaglogger will tranfer to its inner filelogger

*/

namespace miniutil { namespace frame {

    static std::string gettimestamp()
    {
        time_t now = time(NULL);
        std::string tstamp = std::string(ctime(&now));
        return tstamp.substr(0, tstamp.length()-1);
    }

    static std::string getabspath(std::string path)
    {
#ifdef WIN32
        if(path.find(":/")!= std::string::npos || path.find(":\\") != std::string::npos)
            return path;
#else
        if(path.length() > 0 && path[0] == '/')
            return path;
#endif
        std::string exepath = miniutil::runtimeu::getModuleDir();
        return exepath + path;
    }


    LogmsgEnti::LogmsgEnti()
    {
        level = 0;
        logtm = 0;
        threadid = 0;
    }


    Logger::Logger()
    {
        levels = LOG_LEVEL_DEBUG;
        bappendthread = false;
        bappendtm = false;
        bappendthread =false;
    }

	bool Logger::isEnabled(unsigned int level) 
    { 
        return ( this->levels & level) != 0; 
    }

	void Logger::enableLevel( unsigned int l ) 
    { 
        levels = levels | l;
    }
    
    void Logger::setLogLevel( unsigned int l ) 
    { 
        levels = l;
    }

    int Logger::log(const LogmsgEnti& msgenti)
    {
        if(isEnabled(msgenti.level))
        {
            if(should_log(msgenti.logmsg))
            {
                std::stringstream ss;
                if(bappendtm)
                {
                    std::string stm = ctime(&(msgenti.logtm));
                    stm = stm.substr(0, stm.length()-1);
                    ss<<"["<<stm<<"]";
                }
                if(bappendthread)
                    ss<<"["<<msgenti.threadid<<"]";
                ss<<msgenti.logmsg;
                return log_impl(ss.str());
            }
        }
        return 0;
    }

    int Logger::config(miniutil::tinyxml::TiXmlElement* pele)
    {
	    if ( pele == NULL )
		    return -1;
        
        const char* plevel = pele->Attribute("level");
        if(plevel != NULL)
        {
            levels = 0;
            std::string value = plevel;
            if(value.find("all") != std::string::npos)
                enableLevel(LOG_LEVEL_ALL);
            if(value.find("error") != std::string::npos)
                enableLevel( LOG_LEVEL_ERROR );
            if(value.find("crit") != std::string::npos)
                enableLevel( LOG_LEVEL_CRIT );
            if(value.find("warn") != std::string::npos)
                enableLevel( LOG_LEVEL_WARN );
            if(value.find("debug") != std::string::npos)
                enableLevel( LOG_LEVEL_DEBUG );
            if(value.find("trace") != std::string::npos)
                enableLevel( LOG_LEVEL_TRACE );
            if(value.find("audit") != std::string::npos)
                enableLevel( LOG_LEVEL_AUDIT );
        }
        else
        {
            enableLevel(LOG_LEVEL_ALL);
        }

        const char* pappend = pele->Attribute("append");
        if(pappend != NULL)
        {
            std::string value = pappend;
            if(value.find("time") != std::string::npos)
                bappendtm = true;
            if(value.find("thread")!= std::string::npos)
                bappendthread = true;
        }
        return 0;
    }

    //-------------------------------------

    int ConsoleLogger::config(miniutil::tinyxml::TiXmlElement* pele)
    {
        Logger::config(pele);
        return 0;
    }

    int ConsoleLogger::log_impl(const std::string& logmsg)
    {
        std::cout<<logmsg;
        return 0;
    }
    
    //---------------------------------------
    LocalFileLogger::LocalFileLogger()
    {
        plogfile = NULL;
        maxfilesize = 8*1024*1024;
        maxfilenum = 10;
        curfileidx = 0;
        curwritesize = 0;
        savedir = ".";
        mbuseprocessid = false;
        mbusetime = false;
    }

    int LocalFileLogger::config(miniutil::tinyxml::TiXmlElement *pele)
    {
        if(pele == NULL)
            return -1;

        Logger::config(pele);
        const char* pvalue = NULL;
        pvalue = pele->Attribute("maxfilesize");
        if(pvalue != NULL)
            maxfilesize = atoi(pvalue);
        pvalue = pele->Attribute("maxfilenum");
        if(pvalue != NULL)
            maxfilenum = atoi(pvalue);
        pvalue = pele->Attribute("filenmprefix");
        if(pvalue != NULL)
            filenmprefix = pvalue;
        pvalue = pele->Attribute("savedir");
        if(pvalue != NULL)
            savedir = getabspath(pvalue);
        pvalue = pele->Attribute("usepid");
        if(pvalue != NULL)
        {
            if(miniutil::strutil::equalsIgnoreCase(pvalue, "true"))
            {
                mbuseprocessid = true;
            }
        }
        pvalue = pele->Attribute("usetime");
        if(pvalue != NULL)
        {
            if(miniutil::strutil::equalsIgnoreCase(pvalue, "true"))
            {
                mbusetime = true;
            }
        }
        return 0;
    }

    int LocalFileLogger::log_impl(const std::string& logmsg)
    {
        if(plogfile == NULL)
        {
            std::string fn = generate_filenm();
			plogfile = miniutil::fileu::open((savedir+"/"+fn).c_str(), "wb+");
            if(plogfile != NULL)
            {
                std::stringstream ss;
                ss<<"[start]"<<gettimestamp()<<"\n";
                fwrite(ss.str().c_str(), 1, ss.str().length(), plogfile);
            }
            curwritesize = 0;
        }
        else
        {
            if(curwritesize >= maxfilesize)
            {
                std::stringstream ss;
                ss<<"[end]"<<gettimestamp()<<"\n";
                fwrite(ss.str().c_str(), 1, ss.str().length(), plogfile);
                fclose(plogfile);
                std::string fn = generate_filenm();
                //plogfile = fopen((savedir+"/"+fn).c_str(), "wb+");
				plogfile = miniutil::fileu::open((savedir+"/"+fn).c_str(), "wb+");
                if(plogfile != NULL)
                {
                    ss<<"[start]"<<gettimestamp()<<"\n";
                    fwrite(ss.str().c_str(), 1, ss.str().length(), plogfile);
                }
                curwritesize = 0;
            }
        }
        if(plogfile != NULL)
        {
            fwrite(logmsg.c_str(), 1, logmsg.length(), plogfile);
            fflush(plogfile);
            curwritesize += logmsg.length();
        }
        return 0;
    }

    std::string LocalFileLogger::generate_filenm()
    {

        std::stringstream ss;
        if(!filenmprefix.empty())
            ss<<filenmprefix<<"_";
        if(mbuseprocessid)
#ifdef WIN32
            ss<<::GetCurrentProcessId()<<"_";
#else
            ss<<::getpid()<<"_";
#endif
        if(mbusetime)
        {
           struct tm *newtime;
           time_t now;
           time(&now);
           newtime = localtime( &now ); // C4996
           if(newtime != NULL)
           {
               ss<<newtime->tm_year+1900;
               ss.width(2); ss.fill('0'); ss<<newtime->tm_mon+1;
               ss.width(2); ss.fill('0'); ss<<newtime->tm_mday;
			   ss.width(2); ss.fill('0'); ss<<newtime->tm_hour;
			   ss.width(2); ss.fill('0'); ss<<newtime->tm_min;
			   ss<<".log";
           }
        }
        else
        {
            curfileidx = curfileidx%maxfilenum;
            curfileidx ++;
            ss<<"["<<curfileidx<<"].log";
        }
        return ss.str();
    }

    void LocalFileLogger::setfilenmprefix(const std::string& pre)
    {
        filenmprefix = pre;
        miniutil::strutil::replace(filenmprefix, "|", "_");
    }

    //------------------------------------
    TagInnLogger::TagInnLogger(const std::string &tags, Logger* plog, bool ptag)
    {
        std::vector<std::string> alltgs = miniutil::strutil::split(tags, "|");
        for(unsigned int i=0; i < alltgs.size(); i++)
        {
            mapplytags.insert(alltgs[i]);
        }
        plogimpl = plog;
        bprinttag = ptag;
    }

    TagInnLogger::~TagInnLogger()
    {
        if(plogimpl != NULL)
            delete plogimpl;
        plogimpl = NULL;
    }

    //...................

    //auto save log msg with different tag to different file.
    AutoTagFileLogger::AutoTagFileLogger()
    {
    }

    AutoTagFileLogger::~AutoTagFileLogger()
    {
        std::map<std::string, LocalFileLogger*>::iterator msl_iter;
        for(msl_iter = malllogers.begin(); msl_iter != malllogers.end(); 
            msl_iter ++)
        {
            delete msl_iter->second;
        }
    }

    int AutoTagFileLogger::config(miniutil::tinyxml::TiXmlElement* pele)
    {
        if(pele != NULL)
            mconfigxml<< (*pele);
        if(pele->Attribute("exclude") != NULL)
        {
            std::string exclustr = pele->Attribute("exclude");
            std::vector<std::string> vss = miniutil::strutil::split(exclustr, "|");
            for(unsigned int i=0; i< vss.size(); i++)
            {
                msexcludetags.insert(vss[i]);
            }
        }
        return 0;
    }

    int AutoTagFileLogger::log(const LogmsgEnti& msgenti)
    {
        std::string tag = "";
        std::string::size_type pos1, pos2;
        pos1 = msgenti.logmsg.find("[tag:");
        if(pos1 != std::string::npos)
        {
            pos2 = msgenti.logmsg.find("]", pos1);
            tag = msgenti.logmsg.substr(pos1+5, pos2-pos1-5);
        }

        if(tag.empty())
            return -1;

        if(msexcludetags.find(tag) != msexcludetags.end())
            return -1;

        if(malllogers.find(tag) == malllogers.end())
        {
            LocalFileLogger *pnewlog = new LocalFileLogger();
            miniutil::tinyxml::TiXmlDocument tidoc;
            if(!mconfigxml.empty())
            {
                tidoc.Parse(mconfigxml.c_str());
                pnewlog->setfilenmprefix(tag);
                pnewlog->config(tidoc.RootElement());
            }
            malllogers.insert(std::make_pair(tag, pnewlog));
        }
        LogmsgEnti tmpmsg = msgenti;
        tmpmsg.logmsg = msgenti.logmsg.substr(0, pos1)+ msgenti.logmsg.substr(pos2+1);
        malllogers[tag]->log(tmpmsg);
        return 0;
    }

    int AutoTagFileLogger::log_impl(const std::string& logmsg)
    {
        return 0;
        assert(0);
        return 0;
    }

    //------------------------------------
    TagWrapperLogger::TagWrapperLogger()
    {
    }

    TagWrapperLogger::~TagWrapperLogger()
    {
        for(unsigned int i=0; i< allinnloggers.size(); i++)
            delete allinnloggers[i];
        allinnloggers.clear();
    }

    int TagWrapperLogger::config(miniutil::tinyxml::TiXmlElement *pele)
    {
        if(pele == NULL)
            return -1;

        miniutil::tinyxml::TiXmlElement* pelelog = pele->FirstChildElement("filelogger");
        while(pelelog != NULL)
        {
            std::string strtag;
            const char* ptag = pelelog->Attribute("tag");
            if(ptag != NULL)
                strtag = ptag;

            bool bptag = true;
            const char* pprinttag = pelelog->Attribute("printtag");
            if(pprinttag!= NULL && std::string(pprinttag) == "false")
                bptag = false;

            LocalFileLogger* pfllog = new LocalFileLogger();
            pfllog->setfilenmprefix(strtag);
            pfllog->config(pelelog->FirstChildElement("config"));
            allinnloggers.push_back(new TagInnLogger(strtag, pfllog, bptag)); 

            pelelog = pelelog->NextSiblingElement("filelogger");
        }
        
        pelelog = pele->FirstChildElement("consolelogger");
        while(pelelog != NULL)
        {
            std::string strtag;
            const char* ptag = pelelog->Attribute("tag");
            if(ptag != NULL)
                strtag = ptag;

            bool bptag = true;
            const char* pprinttag = pelelog->Attribute("printtag");
            if(pprinttag!= NULL && std::string(pprinttag) == "false")
                bptag = false;

            ConsoleLogger* pcllog = new ConsoleLogger();
            pcllog->config(pelelog->FirstChildElement("config"));
            allinnloggers.push_back(new TagInnLogger(strtag, pcllog, bptag)); 

            pelelog = pelelog->NextSiblingElement("consolelogger");
        }

       // pelelog = pele->FirstChildElement("autotagfilelogger");
       // while(pelelog != NULL)
       // {
       //     std::string strtag;
       //     const char* ptag = pelelog->Attribute("tag");
       //     if(ptag != NULL)
       //         strtag = ptag;

       //     bool bptag = true;
       //     const char* pprinttag = pelelog->Attribute("printtag");
       //     if(pprinttag!= NULL && std::string(pprinttag) == "false")
       //         bptag = false;

       //     AutoTagFileLogger* pnewlog = new AutoTagFileLogger();
       //     pnewlog->config(pelelog->FirstChildElement("config"));
       //     allinnloggers.push_back(new TagInnLogger(strtag, pnewlog, bptag)); 
       //     pelelog = pelelog->NextSiblingElement("autotagfilelogger");
       //}
        return 0;
    }

    int TagWrapperLogger::log(const LogmsgEnti& msgenti)
    {
        std::string tag = "";
        std::string::size_type pos1, pos2;
        pos1 = msgenti.logmsg.find("[tag:");
        if(pos1 != std::string::npos)
        {
            pos2 = msgenti.logmsg.find("]", pos1);
            tag = msgenti.logmsg.substr(pos1+5, pos2-pos1-5);
            for(unsigned int i=0; i< allinnloggers.size(); i++)
            {
                if(allinnloggers[i]->mapplytags.find("*") != allinnloggers[i]->mapplytags.end() ||
                    allinnloggers[i]->mapplytags.find(tag) != allinnloggers[i]->mapplytags.end())
                {
                    LogmsgEnti tmpmsg = msgenti;
                    if(allinnloggers[i]->bprinttag)
                    {
                        tmpmsg.logmsg = msgenti.logmsg.substr(0, pos1+1)+ msgenti.logmsg.substr(pos1+5);
                        allinnloggers[i]->plogimpl->log(tmpmsg);
                    }
                    else
                    {
                        tmpmsg.logmsg = msgenti.logmsg.substr(0, pos1)+ msgenti.logmsg.substr(pos2+1);
                        allinnloggers[i]->plogimpl->log(tmpmsg);
                    }
                }
            }
        }
        return 0;
    }

    int TagWrapperLogger::log_impl(const std::string& logmsg)
    {
        assert(0);
        return 0;
    }


	//------------------------------------
	AotuRunTimeLogger::AotuRunTimeLogger()
	{
	}

	AotuRunTimeLogger::~AotuRunTimeLogger()
	{
	}

	int AotuRunTimeLogger::config(miniutil::tinyxml::TiXmlElement* pele)
	{
        if(pele->Attribute("exclude") != NULL)
        {
            std::string exclustr = pele->Attribute("exclude");
            std::vector<std::string> vss = miniutil::strutil::split(exclustr, "|");
            for(unsigned int i=0; i< vss.size(); i++)
            {
                msexcludetags.insert(vss[i]);
            }
        }
		if(pele->Attribute("savedir") != NULL)
		{
			std::string path = pele->Attribute("savedir");
			msavepath = getabspath(path);
		}
        return 0;

	}

	int AotuRunTimeLogger::log(const LogmsgEnti& msgenti)
	{
        std::string tag = "";
        std::string::size_type pos1, pos2;
        pos1 = msgenti.logmsg.find("[runtime:");
        if(pos1 != std::string::npos)
        {
            pos2 = msgenti.logmsg.find("]", pos1);
			if(pos2 == std::string::npos)
				return -1;
            tag = msgenti.logmsg.substr(pos1+9, pos2-pos1-9);
        }

        if(tag.empty())
            return -1;

        if(msexcludetags.find(tag) != msexcludetags.end())
            return -1;

		FILE* pfile = NULL;
		std::string fnm = msavepath + "/" + tag + ".log";
		pfile = miniutil::fileu::open(fnm.c_str(), "wb");
		if(pfile != NULL)
		{
			std::string tmpmsg = msgenti.logmsg.substr(0, pos1)+ msgenti.logmsg.substr(pos2+1);
			fwrite(tmpmsg.c_str(), 1, tmpmsg.length(), pfile);
			fclose(pfile);
			pfile = NULL;
		}
		return 0;
	}

	int AotuRunTimeLogger::log_impl(const std::string& logmsg)
	{
		assert(0);
		return 0; 
		//not include any other logger
	}

    //------------------------------------
    //---
    void LogService::InitService()
    {
        //declare all default log implementation..
        miniutil::srtti::srtti::_declare< miniutil::frame::ConsoleLogger > ( "consolelogger" );
        miniutil::srtti::srtti::_declare< miniutil::frame::LocalFileLogger > ( "filelogger" );
        miniutil::srtti::srtti::_declare< miniutil::frame::TagWrapperLogger > ( "tagfilelogger" ); //kept for compatible
        miniutil::srtti::srtti::_declare< miniutil::frame::TagWrapperLogger > ( "taglogger" );
        miniutil::srtti::srtti::_declare< miniutil::frame::AutoTagFileLogger > ( "autotagfilelogger" );
		miniutil::srtti::srtti::_declare< miniutil::frame::AotuRunTimeLogger > ( "autoruntimelogger" );
    }

    /*
        <loggers>
            <logger name="consolelogger" >
                <conf level="debug|crit"></conf>
            </logger>
        </loggers>
    */
    LogService::LogService(unsigned int sleepms)
        :ThreadService(sleepms)
    {
    }

    LogService::~LogService()
    {
        for(unsigned int i=0; i< vinploggers.size(); i++)
        {
            miniutil::srtti::srtti::_destroyInstance(vinploggers[i].logclsnm.c_str(), vinploggers[i].plog);
        }
        vinploggers.clear();
    }

    int LogService::config(miniutil::tinyxml::TiXmlElement* pele)
    {
        miniutil::tinyxml::TiXmlElement *pchild = pele->FirstChildElement("logger");
        while(pchild != NULL)
        {
            const char* pclsnm = pchild->Attribute("name");
            if(pclsnm != NULL)
            {
		        Logger * plog = (Logger*) miniutil::srtti::srtti::_newInstance(pclsnm);
                if(plog != NULL)
                {
                    miniutil::tinyxml::TiXmlElement* plogconf = NULL;
                    plogconf = pchild->FirstChildElement("config");
                    if(plogconf != NULL)
                        plog->config(plogconf);
                    loggerenti enti;
                    enti.plog = plog;
                    enti.logclsnm = pclsnm;
                    vinploggers.push_back(enti);
                }
            }
            pchild = pchild->NextSiblingElement();
        }
        return 0;
    }

    //static std::string gettimestamp()
    //{
    //    std::stringstream ss;
    //    time_t now = time(NULL);
    //    struct tm* nowtime = localtime(&now);
    //    ss<<nowtime->tm_mon+1<<":"<<nowtime->tm_mday<<":"
    //        <<nowtime->tm_hour<<":"<<nowtime->tm_min<<":"
    //        <<nowtime->tm_sec;
    //    return ss.str();
    //}


    int LogService::log(int level, const std::string& logmsg)
    {
        if(this->IsRunning())
        {
            LogmsgEnti enti;
            enti.level = level;
            enti.logmsg = logmsg;
            enti.logtm = time(NULL);
    #ifdef WIN32
            enti.threadid = GetCurrentThreadId();
    #else
            enti.threadid = pthread_self();
    #endif

            miniutil::auto_lock kk(&logmtx);

            vlogmsgs.push_back(enti);
        }
        else
        {
            miniutil::auto_lock kk(&logmtx);
            std::cout<<logmsg;
        }
        return 0;
    }

    int LogService::WorkingTask()
    {
        miniutil::auto_lock kk(&logmtx);
        for(unsigned int i=0; i< vlogmsgs.size(); i++)
        {
            for(unsigned int j=0; j< vinploggers.size(); j++)
            {
                std::string logmsg;
                vinploggers[j].plog->log(vlogmsgs[i]);
            }
        }
        vlogmsgs.clear();
        return 0;
    }

    LogService galoggservice;

    autoentry_logger::autoentry_logger(const char* fn, const char* func, int line)
    {
	    sfn = fn;
	    sfunc = func;
	    mline = line;
        galoggservice.log(Logger::LOG_LEVEL_TRACE, std::string("[tag:trace]")+miniutil::strutil::format("(%s:%s:%d)Enter\n", fn, func, line));
    }

    autoentry_logger::~autoentry_logger()
    {
        galoggservice.log(Logger::LOG_LEVEL_TRACE, std::string("[tag:trace]")+miniutil::strutil::format("(%s:%s:%d)Leave\n", sfn.c_str(), sfunc.c_str(), mline));
    }

} }
